package runnerworm.core;

import java.awt.*;
import java.util.*;

/**
 * <p>Title: Beholder</p>
 * <p>Description: This class is the almighty Beholder. It observes and, if
 * something happens, it launches the correspondent event.</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */

public class Beholder extends Thread {

  /**
   * Will provide to launch all the BeholderEvents.
   */
  private BeholderEventLauncher beholderEventLauncher;
  /**
   * The worm - that position will be checked.
   */
  private Worm worm;
  /**
   * The tunnel - where the worm can collide.
   */
  private Tunnel tunnel;
  /**
   * Constructor. Needs the worm and the tunnel. Creates the event launcher.
   * @param worm The worm to be observed.
   * @param tunnel The tunnel the worm is running through.
   */
  public Beholder(Worm worm, Tunnel tunnel) {
    this.worm = worm;
    this.tunnel = tunnel;
    beholderEventLauncher = new BeholderEventLauncher();
  }

  /**
   * Runs the thread. I mean, it will make the beholder observe and inform.
   * Its kind simple: Checks if the worm has hit something, if so, bang! Game
   * is over. Point scored case not!
   */
  public void run() {
    while (worm.isAlive() && tunnel.isMoving()) {
      if (checkCollision(worm.getDraw(), tunnel))
        beholderEventLauncher.createWormCollisionBeholderEvent();
      else
        beholderEventLauncher.createPointScoredBeholderEvent();
      if (tunnel.hasEnded())
        beholderEventLauncher.createTunnelEndedBeholderEvent();
      try {
        sleep(30);
      } catch (InterruptedException e) {
        System.out.println("Shit happens");
      }
    }
  }

  /**
   * Checks if the worm has collided to anything.
   * @param polygons The polygon array representing the worm.
   * @param tunnel The tunnel.
   * @return True if there was a collision, false case not.
   */
  private synchronized boolean checkCollision(Polygon[] polygons, Tunnel tunnel) {
    int arrayLength = polygons.length;
    for (int k = 0; k < arrayLength; k++) {
      if (tunnel.checkCollision(polygons[k]))
        return true;
    }
    return false;
  }

  /**
   * Adds a listener to BeholderEvents
   * @param l The BeholderInterface listener.
   */
  public void addBeholderListener(BeholderListener l) {
    beholderEventLauncher.addBeholderListener(l);
  }



}

/**
 * <p>Title: BeholderListener</p>
 * <p>Description: This will be the interface responsable for the event
 * launched by the Beholder. It will be implemented to provide what
 * to do when the Beholder sinalizes that something happens.</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */
interface BeholderListener
    extends EventListener {

  /**
   * Provides what to do when the worm collides to a wall.
   * @param evt The WormCollisionBeholderEvent captured.
   */
  void wormHasCollided(WormCollisionBeholderEvent evt);
  /**
   * Provides what to do when a point is scored.
   * @param evt The event captured.
   */
  void hasScored(PointScoredBeholderEvent evt);
  /**
   * Provides what to do when the tunnel has ended.
   * @param evt The event captured.
   */
  void tunnelHasEnded(TunnelEndedBeholderEvent evt);

}

/**
 * <p>Title: BeholderEventLauncher</p>
 * <p>Description: This class has only one meaning: launch BeholderEvents.</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */

class BeholderEventLauncher extends Component{
  /**
   * This is the event queue.
   */
  private static EventQueue eventQueue;
  /**
   * This will be the BeholderEvent listener
   */
  private BeholderListener listener;

  /**
   * The constructor. It will enable the events launching.
   */
  public BeholderEventLauncher() {
    this.eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    enableEvents(0);
  }

  /**
   * Adds a listener to this class' events.
   * @param l The BeholderEvents listener.
   */
  public void addBeholderListener(BeholderListener l) {
    listener = l;
  }

  /**
   * Maybe puts the event on the event queue, or something like that.
   * @param evt The event will be put on the queue
   */
  public void processEvent(AWTEvent evt) {
    if (evt instanceof BeholderEvent) {
      if (listener != null)
        switch (evt.getID()) {
          case (BeholderEvent.WORM_COLLISION):
            listener.wormHasCollided((WormCollisionBeholderEvent)evt);
            break;
          case (BeholderEvent.POINT_SCORED):
            listener.hasScored((PointScoredBeholderEvent)evt);
            break;
          case (BeholderEvent.TUNNEL_END):
            listener.tunnelHasEnded((TunnelEndedBeholderEvent)evt);
        }
    }
    else
      super.processEvent(evt);
  }

  /**
   * Creates and launches a WormCollisionBeholderEvent.
   */
  public void createWormCollisionBeholderEvent() {
    eventQueue.postEvent(new WormCollisionBeholderEvent(this));
  }

  /**
   * Creates and launches a PointScoredBeholderEvent.
   */
  public void createPointScoredBeholderEvent() {
    eventQueue.postEvent(new PointScoredBeholderEvent(this));
  }

  /**
   * Creates and launches a TunnelEndedBeholderEvent.
   */
  public void createTunnelEndedBeholderEvent() {
    eventQueue.postEvent(new TunnelEndedBeholderEvent(this));
  }

}


/**
 * <p>Title: BeholderEvent</p>
 * <p>Description: This will be the event that represents a Beholder
 * sinalization</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */
class BeholderEvent
    extends AWTEvent {

  /**
   * This will be the id for the WormCollisionEvent.
   */
  public static final int WORM_COLLISION = AWTEvent.RESERVED_ID_MAX + 9000;
  /**
   * This will be the id for the PointScoredBeholderEvent.
   */
  public static final int POINT_SCORED = AWTEvent.RESERVED_ID_MAX + 9001;
  /**
   * This will be the id for the TunnelEndedBeholderEvent.
   */
  public static final int TUNNEL_END = AWTEvent.RESERVED_ID_MAX + 9002;

  /**
   * The constructor. Will receive the event's source and its id.
   * @param beholderEventLauncher The event's source.
   * @param id The event's identification.
   */
  public BeholderEvent(BeholderEventLauncher beholderEventLauncher, int id) {
    super(beholderEventLauncher, id);
  }
}

/**
 * <p>Title: WormCollisionBeholderEvent</p>
 * <p>Description: This will be the event that represents a worm collision</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */
class WormCollisionBeholderEvent
    extends BeholderEvent {
  /**
   * The constructor. Will receive the event's source.
   * @param beholderEventLauncher The event's source.
   */
  public WormCollisionBeholderEvent(BeholderEventLauncher beholderEventLauncher) {
    super(beholderEventLauncher, BeholderEvent.WORM_COLLISION);
  }
}

/**
 * <p>Title: PointScoredBeholderEvent</p>
 * <p>Description: This represents a point scored.</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */

class PointScoredBeholderEvent
    extends BeholderEvent {
  /**
   * Will need the event's source.
   * @param source The event's source.
   */
  public PointScoredBeholderEvent(BeholderEventLauncher source) {
    super(source, POINT_SCORED);
  }
}


/**
 * <p>Title: TunnelEndedBeholderEvent</p>
 * <p>Description: Represents the end of the tunnel.</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */
class TunnelEndedBeholderEvent
    extends BeholderEvent {

  /**
   * Will need the event's source.
   * @param source The event source.
   */
  public TunnelEndedBeholderEvent(BeholderEventLauncher source) {
    super(source, TUNNEL_END);
  }
}